﻿/*--------------------------------------------
 * -------------------------------------------
 * --- CLASS MEDIAN FILTER EXECUTE -----------
 * -------------------------------------------
 * Created By: Roy Scheefhals ----------------
 * 
 * usage: Gebruikt het median filter en zorgt
 * voor de threading en het maken van een Myimage
 * En gebruikt de MedianFilter class voor
 * het uitvoeren van de median
 * (maakt ook nieuwe bitmaps aan)
 * 
 * MODIFIED ON 02-04-2012
 *      Made the Multithreadable median filter for Colour
 *      Made Multithreadable variable!
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

namespace LibraryCS
{
    public class MedianExecute : VisionFilter
    {
        private bool gray_channel = false; // true is generate gray channel , false is all RGB channels

        private int amountOfThreads = 3;

        private byte[][] GrayChannels;

        private byte[][] RedChannels;
        private byte[][] GreenChannels;
        private byte[][] BlueChannels;

        private bool[] channelsDone;

        //private Bitmap SourceBitmap;
        //private Bitmap OutputBitmap;
        private VImage OutputImage;
        private VImage SourceImage;

        public MedianExecute(String name)
            : base(name)
        {
            base._KernelSize = 5;
        }

        public override System.Drawing.Bitmap DoAlgorithm(Bitmap InputBitmap)
        {
            SourceImage = new VImage(InputBitmap);
            OutputImage = new VImage(SourceImage.Width, SourceImage.Height);
            DoMedian();
            return OutputImage.localBitmap;
        }

        public VImage ExecuteMedian(VImage InputImage)
        {
            SourceImage = InputImage;
            OutputImage = new VImage(SourceImage.Width, SourceImage.Height);
            DoMedian();

            return OutputImage;
        }

        public VImage ExecuteMedian(Bitmap InputBitmap)
        {
            SourceImage = new VImage(InputBitmap);
            OutputImage = new VImage(SourceImage.Width, SourceImage.Height);
            DoMedian();
            return OutputImage;
        }
        //always demands a VImage
        private void DoMedian()
        {

            MedianFilter mf = new MedianFilter(KernelSize);
            if (!gray_channel)
            {

                int threadsPerChannel = amountOfThreads / 3; // amount of parts ( not 0 ) 

                RedChannels = new byte[threadsPerChannel][];
                GreenChannels = new byte[threadsPerChannel][];
                BlueChannels = new byte[threadsPerChannel][];
                channelsDone = new bool[threadsPerChannel * 3];
                if (threadsPerChannel == 0) // less then 3 threads! process as 1 thread and return
                {
                    OutputImage.RedPixels = mf.DoMedianFilter(SourceImage.RedPixels, SourceImage);
                    OutputImage.GreenPixels = mf.DoMedianFilter(SourceImage.GreenPixels, SourceImage);
                    OutputImage.BluePixels = mf.DoMedianFilter(SourceImage.BluePixels, SourceImage);

                    //OutputImage.localImage = OutputImage.returnNewBitmapColour(OutputImage.RedPixels, OutputImage.GreenPixels, OutputImage.BluePixels);
                    return;
                }
                else
                {
                    int rowDivider = SourceImage.Height / threadsPerChannel;
                    int restRow = SourceImage.Height % threadsPerChannel;

                    Boolean hasHalfChannels = false;
                    if (threadsPerChannel > 1)
                        hasHalfChannels = true;

                    //Define Threads ( split up jobs ( rows to do ) and start them ) 
                    for (int i = 0; i < threadsPerChannel; i++)
                    {
                        ThreadStart jobR, jobG, jobB;

                        int ThreadId = i * 3;
                        int inew = i;
                        if (inew == threadsPerChannel - 1)
                        {
                            jobR = delegate { mf.DoMedianFilterThreaded(SourceImage.RedPixels, this, SourceImage, inew * rowDivider, (rowDivider + ((inew) * rowDivider)) + restRow, hasHalfChannels, ThreadId); };
                            jobG = delegate { mf.DoMedianFilterThreaded(SourceImage.GreenPixels, this, SourceImage, inew * rowDivider, (rowDivider + ((inew) * rowDivider)) + restRow, hasHalfChannels, ThreadId + 1); };
                            jobB = delegate { mf.DoMedianFilterThreaded(SourceImage.BluePixels, this, SourceImage, inew * rowDivider, (rowDivider + ((inew) * rowDivider)) + restRow, hasHalfChannels, ThreadId + 2); };
                        }
                        else
                        {
                            jobR = delegate { mf.DoMedianFilterThreaded(SourceImage.RedPixels, this, SourceImage, inew * rowDivider, rowDivider + ((inew) * rowDivider), hasHalfChannels, ThreadId); };
                            jobG = delegate { mf.DoMedianFilterThreaded(SourceImage.GreenPixels, this, SourceImage, inew * rowDivider, rowDivider + ((inew) * rowDivider), hasHalfChannels, ThreadId + 1); };
                            jobB = delegate { mf.DoMedianFilterThreaded(SourceImage.BluePixels, this, SourceImage, inew * rowDivider, rowDivider + ((inew) * rowDivider), hasHalfChannels, ThreadId + 2); };
                        }
                        channelsDone[ThreadId] = false;
                        channelsDone[ThreadId + 1] = false;
                        channelsDone[ThreadId + 2] = false;
                        new Thread(jobR).Start();
                        new Thread(jobG).Start();
                        new Thread(jobB).Start();
                    }

                    // wait until all the threads are done
                    lock (this)
                    {
                        Monitor.Wait(this);
                    }
                    //restore the partial channels (if needed) to 1 channel and put them in the outputImage
                    if (hasHalfChannels)
                    {
                        OutputImage.RedPixels = OutputImage.restorePartialArraysToFullArray(RedChannels, rowDivider, restRow);
                        OutputImage.GreenPixels = OutputImage.restorePartialArraysToFullArray(GreenChannels, rowDivider, restRow);
                        OutputImage.BluePixels = OutputImage.restorePartialArraysToFullArray(BlueChannels, rowDivider, restRow);
                    }
                    else
                    {
                        OutputImage.RedPixels = RedChannels[0];
                        OutputImage.GreenPixels = GreenChannels[0];
                        OutputImage.BluePixels = BlueChannels[0];
                    }
                    //OutputImage.localImage = OutputImage.returnNewBitmapColour(OutputImage.RedPixels, OutputImage.GreenPixels, OutputImage.BluePixels);
                    return;
                }
            }
            //STUFF IS ONLY GRAY
            else
            {
                if (amountOfThreads <= 1) // only 1 threaded Grayvalues median
                {
                    OutputImage.GrayPixels = mf.DoMedianFilter(SourceImage.GrayPixels, SourceImage);
                    //OutputImage.localImage = OutputImage.returnNewBitmapOutput(OutputImage.GrayPixels);
                }
                else //multithreaded GrayValues Median
                {
                    GrayChannels = new byte[amountOfThreads][];
                    channelsDone = new bool[amountOfThreads];

                    int rowDivider = SourceImage.Height / amountOfThreads;
                    int restRow = SourceImage.Height % amountOfThreads;

                    //Define Threads ( split up jobs ( rows to do ) and start them ) 
                    for (int i = 0; i < amountOfThreads; i++)
                    {
                        int Threadid = i;
                        ThreadStart job;
                        if (Threadid == amountOfThreads - 1)
                        {
                            job = delegate { mf.DoMedianFilterThreaded(SourceImage.GrayPixels, this, SourceImage, Threadid * rowDivider, (rowDivider + ((Threadid) * rowDivider)) + restRow, true, Threadid); };
                        }
                        else
                        {
                            job = delegate { mf.DoMedianFilterThreaded(SourceImage.GrayPixels, this, SourceImage, Threadid * rowDivider, rowDivider + ((Threadid) * rowDivider), true, Threadid); };
                        }
                        channelsDone[Threadid] = false;
                        new Thread(job).Start();
                    }
                    lock (this)
                    {
                        Monitor.Wait(this);
                    }
                    //restore the partial arrays to 1 array and put him in the OutputImage
                    OutputImage.GrayPixels = OutputImage.restorePartialArraysToFullArray(GrayChannels, rowDivider, restRow);
                    //OutputImage.localImage = OutputImage.returnNewBitmapOutput(OutputImage.GrayPixels);
                }
                return;
            }
        }

        //Notify vanuit de MedianFilter klasse
        public void notifyMeWithChannel(byte[] channel, int id)
        {
            if (!gray_channel)//lolol is color
            {
                int threadpart = id / 3; // zero based

                int channelpart = id % 3;
                if (channelpart == 0) // channel red
                {
                    RedChannels[threadpart] = channel;
                }
                else if (channelpart == 1) // channel green
                {
                    GreenChannels[threadpart] = channel;
                }
                else if (channelpart == 2) // channel blue
                {
                    BlueChannels[threadpart] = channel;
                }
                channelsDone[id] = true;
            }
            else //its Gray... problem? 
            {
                GrayChannels[id] = channel;
                channelsDone[id] = true;
            }

            bool allChannelsDone = true; // preset true
            for (int i = 0; i < channelsDone.Length; i++) // if any channel is not done yet... set channelsdone to false
            {
                if (channelsDone[i] == false) allChannelsDone = false;
            }

            if (allChannelsDone)
            {
                lock (this)
                {
                    Monitor.Pulse(this);
                }
            }
        }

        public bool hasDoneGray()
        {
            if (gray_channel) return true;
            else return false;
        }

        /// <summary>
        /// <para>Function to set the amount of threads used for the median filter</para>
        /// <para>For a Color median we use a multiple of 3 amount of thread eg: 3..6..9..12.. </para>
        /// </summary>
        /// <param name="threads"></param>
        public void setAmountOfThreads(int threads)
        {
            amountOfThreads = threads;
        }

        /// <summary>
        /// Sets the Median Mode to Gray or Colour
        /// </summary>
        /// <param name="Gray">True = Gray_channel only. False = Colour channel only</param>
        public void setGrayMedian(bool Gray)
        {
            gray_channel = Gray;
        }

        #region GUI Functions
        /* 
         * Toegevoegd door: Duncan Jenkins
        */
        public override SettingsPropertyBox createControls()
        {
            SettingsPropertyBox propertyControls = new SettingsPropertyBox();

            //Label for Colorchannels
            Label Glb = new Label();
            Glb.Text = "Color Median?";
            Glb.AutoSize = true;

            //textbox for Colorchannels
            CheckBox Gcb = new CheckBox();
            Gcb.Dock = DockStyle.Left;
            Gcb.Checked = !gray_channel;

            // Create a Label indictating the type of setting this is
            Label Klb = new Label();
            Klb.AutoSize = true;
            Klb.Text = "Kernel Size";
            Klb.Name = "lbKernelSize";

            // Create a TextBox Control for setting the kernel size
            TextBox Ktb = new TextBox();
            Ktb.Name = "tbKernelSize";
            Ktb.Text = KernelSize.ToString();
            Ktb.AcceptsReturn = false;
            Ktb.Dock = DockStyle.Fill;
            Ktb.MaxLength = 4;
            Ktb.Multiline = false;

            //Multithread label
            Label Mlb = new Label();
            Mlb.Text = "amount of threads";
            Mlb.AutoSize = true;

            //MultiThread Textbox 
            TextBox Mtb = new TextBox();
            Mtb.Text = amountOfThreads.ToString();
            Mtb.Dock = DockStyle.Fill;
            Mtb.MaxLength = 2;
            Mtb.Multiline = false;
            // Add the controls to the list
            //propertyControls.AddLine(Glb, Gcb, null, null);
            propertyControls.AddLine(Klb, Ktb, null, null);
            propertyControls.AddLine(Mlb, Mtb, null, null);

            // Return the list
            return propertyControls;
        }

        /* 
         * Toegevoegd door: Duncan Jenkins
        */
        public override void setSettings(List<object> settings)
        {
            try
            {
                //gray_channel = !(bool)settings[0];
                KernelSize = Int32.Parse((string)settings[0]);
                amountOfThreads = Int32.Parse((string)settings[1]);
            }
            catch (FormatException)
            {
                MessageBox.Show("Wrong Format has been entered.\nPlease enter integers\nNew Settings have been discarded for " + Name, "Format Error", MessageBoxButtons.OK, MessageBoxIcon.Hand);
            }
            Debug.WriteLine("Settings entered: " + KernelSize);


        }
        #endregion
    }
}
